package cn.zbacm.api.security.service;

import cn.zbacm.api.security.CustomUser;
import cn.zbacm.api.security.dto.JwtSubject;
import cn.zbacm.api.user.entity.User;
import cn.zbacm.api.user.entity.UserRole;
import cn.zbacm.api.user.repository.UserRoleRepository;
import cn.zbacm.api.user.repository.UserSessionRepository;
import cn.zbacm.api.user.service.UserSessionService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.Date;
import java.util.List;

@Slf4j
@Component
public class JwtTokenProvider {
  @Value("${zbacm.jwt.secret}")
  private String jwtSecret;

  @Value("${zbacm.jwt.expiration-time}")
  private int jwtExpirationTime;

  @Autowired
  private UserRoleRepository userRoleRepository;

  @Autowired
  private UserSessionRepository userSessionRepository;

  @Autowired
  private UserSessionService userSessionService;

  private ObjectMapper objectMapper = new ObjectMapper();

  public String generateToken(Authentication authentication) throws JsonProcessingException {
    UserDetails userDetails = (UserDetails) authentication.getPrincipal();
    User user = ((CustomUser) userDetails).getUser();
    int userId = user.getId();
    log.info("start to generateToken for user id: {}", userId);
    List<UserRole> userRoles = userRoleRepository.findByUserId(userId);
    JwtSubject jwtSubject = new JwtSubject();
    jwtSubject.setUserId(user.getId());
    jwtSubject.setHasRole(!userRoles.isEmpty());
    log.info("userRoles.isEmpty() => {}", userRoles.isEmpty());

    Date now = new Date();
    Date expiryDate = new Date(now.getTime() + jwtExpirationTime);
    String token =
            Jwts.builder()
                    .setSubject(objectMapper.writeValueAsString(jwtSubject))
                    .setIssuedAt(now)
                    .setExpiration(expiryDate)
                    .signWith(SignatureAlgorithm.HS512, jwtSecret)
                    .compact();

    if (userRoles.isEmpty()) {
      userSessionService.keep(user, token);
    }
    return token;
  }

  JwtSubject validateToken(String authToken) {
    log.debug("start validating token");
    if (StringUtils.isEmpty(authToken)) {
      log.debug("token is empty");
      return null;
    }

    try {
      log.debug("start parsing claim");
      Claims claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken).getBody();
      if (userSessionRepository.existsByTokenAndIsDisabledTrue(authToken)) {
        log.info("user session is disabled");
        return null;
      }
      return objectMapper.readValue(claims.getSubject(), JwtSubject.class);
    } catch (SignatureException ex) {
      log.error("Invalid JWT signature");
    } catch (MalformedJwtException ex) {
      log.error("Invalid JWT token");
    } catch (ExpiredJwtException ex) {
      log.info("Expired JWT token");
    } catch (UnsupportedJwtException | IOException ex) {
      log.error("Unsupported JWT token");
    } catch (Exception ex) {
      log.error("Unknown JWT token: " + ex.getMessage());
    }
    return null;
  }
}
